<?php
/*
 +----------------------------------------------------------------------
 + Title        : GenModel 控制器
 + Author       : Randy_chen
 + Version      : V1.0.0.1
 + Initial-Time : 2021/2/28 8:13
 + Last-time    : 2021/2/28 8:13+ chen2
 + Desc         : GenModel
 +----------------------------------------------------------------------
*/

namespace app\command\gen;

use think\facade\Config;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\facade\Db;
use think\Exception;
use think\helper\Str;

class Curd extends Command
{
    protected function configure()
    {
        $this->setName('gen:curd')
             ->addOption('table', 't', Option::VALUE_REQUIRED, 'table name without prefix', null)
             ->addOption('module', 'm', Option::VALUE_REQUIRED, 'table name without prefix', 'app')
             ->addOption('file', 'f', Option::VALUE_REQUIRED, 'table name without prefix', 'csvm')
             ->setDescription(' 自动生成CURD ');
    }

    protected function execute(Input $input, Output $output)
    {
        $table = $input->getOption('table') ?: '';
        $module = $input->getOption('module') ?: '';
        $files = $input->getOption('file') ?: 'csvm';
        $files = str_split($files);

        $this->_execute($table, $module, $files);
        $output->writeln("gen curd success");
    }

    protected $stubList = [];

    public function _execute($table, $module, $files)
    {
        $moduleName = $module ?? 'app';
        $validateModuleName = $module ?? 'common';
        $modelModuleName = $module ?? 'common';
        $modelTableType = 'table';
        $prefix = env('database_prefix', '');
        $modelName = stripos($table, $prefix) === 0 ? substr($table, strlen($prefix)) : $table;
        $model = Str::studly($modelName);
        $modelTableTypeName = $modelTableName = $table;
        $typeAttrList = [];

        //从数据库中获取表字段信息
        $sql = "SELECT * FROM `information_schema`.`columns` "
               . "WHERE  table_name = ? and TABLE_SCHEMA  = ? "
               . "ORDER BY ORDINAL_POSITION";

        //加载主表的列
        $columnList = Db::query($sql, [$table, env('database_db')]);

        $fieldArr = [];

        foreach ($columnList as $k => $v) {
            $this->typeAttr($typeAttrList, $v['COLUMN_NAME'], $v['COLUMN_TYPE']);
            $fieldArr[] = $v['COLUMN_NAME'];
        }

        //模型
        [
            $modelNamespace,
            $modelName,
            $modelFile,
            $modelArr,
            $notes
        ] = $this->getModelData($modelModuleName, $model, $modelName, $columnList);

        [
            $controllerNamespace,
            $controllerName,
            $controllerFile,
            $controllerArr
        ] = $this->getControllerData($moduleName, $modelName, $modelName);

        //验证器
        [
            $validateNamespace,
            $validateName,
            $validateFile,
            $validateArr,
            $validateNotes
        ] = $this->getValidateData($validateModuleName, $modelName, $modelName, $columnList);

        //逻辑层处理
        [$logicNamespace, $logicName, $logicFile, $logicArr] = $this->getLogicData($moduleName, $modelName, $modelName);

        $addList = [];

        $editList = [];
        $javascriptList = [];
        $langList = [];
        $field = 'id';
        $order = 'id';
        $priDefined = false;
        $priKey = '';
        $relationPrimaryKey = '';
        foreach ($columnList as $k => $v) {
            if ($v['COLUMN_KEY'] == 'PRI') {
                $priKey = $v['COLUMN_NAME'];
                break;
            }
        }
        if (!$priKey) {
            throw new Exception('Primary key not found!');
        }
        $modelTableInfo = Db::query("SHOW TABLE STATUS LIKE '{$table}'", [], true);
        $tableComment = $modelTableInfo[0]['Comment'];
        $tableComment = mb_substr($tableComment, -1) == '表' ? mb_substr($tableComment, 0, -1) . '管理' : $tableComment;


        $data = [
            'controllerNamespace' => $controllerNamespace,
            'validateNamespace' => $validateNamespace,
            'validateName' => $validateName,
            'validateArr' => $validateArr,
            'validateNotes' => $validateNotes,
            'controllerName' => $controllerName,
            'logicNamespace' => $logicNamespace,
            'logicName' => $logicName,
            'moduleName' => $moduleName,
            'extendModule' => $moduleName == 'cms' ? ucfirst('admin') : ucfirst($moduleName),
            'modelNamespace' => $modelNamespace,
            'modelName' => $modelName,
            'modelTableName' => $modelTableName,
            'modelTableType' => $modelTableType,
            'modelTableTypeName' => $modelTableTypeName,
            'tableComment' => $tableComment,
            'pk' => $priKey,
            'order' => $order,
            'table' => $table,
            'tableName' => $modelTableName,
            'addList' => $addList,
            'editList' => $editList,
            'javascriptList' => $javascriptList,
            'langList' => $langList,
            'modelAutoWriteTimestamp' => in_array('create_time', $fieldArr) || in_array('update_time', $fieldArr) ? "'int'" : 'false',
            'createTime' => in_array('create_time', $fieldArr) ? "'create_time'" : 'false',
            'updateTime' => in_array('update_time', $fieldArr) ? "'update_time'" : 'false',
            'notes' => $notes,
            'typeAttrList' => implode(",\n", $typeAttrList),
            'appendAttrList' => '',//implode(",\n", $appendAttrList),
            'getEnumList' => '',//implode("\n\n", $getEnumArr),
            'getAttrList' => '',//implode("\n\n", $getAttrArr),
            'setAttrList' => '',//implode("\n\n", $setAttrArr),
            'modelInit' => '',//$modelInit,
        ];
        $order = $priKey;
        // 生成模型
        in_array('m', $files) && $this->writeToFile('model', $data, $modelFile);
        //生成验证器
        in_array('v', $files) && $this->writeToFile('validate', $data, $validateFile);
        //生成控制器
        in_array('c', $files) && $this->writeToFile('controller', $data, $controllerFile);
        //生成logic
        in_array('s', $files) && $this->writeToFile('service', $data, $logicFile);
        return true;
    }

    protected function typeAttr(&$typeAttrList, $field, $type)
    {
        $tmp = '';
        switch ($type) {
            case 'int':
            case 'decimal':
                $tmp = 'string';
                break;
            case 'json':
                $tmp = 'json';
                break;
        }
        if($tmp){
            $typeAttrList[] = <<<EOD
        '{$field}' => '{$tmp}'
EOD;
        }
    }


    /**
     * 写入到文件
     *
     * @param string $name
     * @param array  $data
     * @param string $pathname
     *
     * @return mixed
     */
    protected function writeToFile($name, $data, $pathname)
    {
        foreach ($data as $index => &$datum) {
            $datum = is_array($datum) ? '' : $datum;
        }
        $content = $this->getReplacedStub($name, $data);

        if (!is_dir(dirname($pathname))) {
            mkdir(dirname($pathname), 0755, true);
        }
        return file_put_contents($pathname, $content);
    }

    /**
     * 获取替换后的数据
     *
     * @param string $name
     * @param array  $data
     *
     * @return string
     */
    protected function getReplacedStub($name, $data)
    {
        foreach ($data as $index => &$datum) {
            $datum = is_array($datum) ? '' : $datum;
        }
        unset($datum);
        $search = $replace = [];
        foreach ($data as $k => $v) {
            $search[] = "{%{$k}%}";
            $replace[] = $v;
        }
        $stubname = $this->getStub($name);
        if (isset($this->stubList[$stubname])) {
            $stub = $this->stubList[$stubname];
        } else {
            $this->stubList[$stubname] = $stub = file_get_contents($stubname);
        }
        $content = str_replace($search, $replace, $stub);
        return $content;
    }

    /**
     * 获取基础模板
     *
     * @param string $name
     *
     * @return string
     */
    protected function getStub($name)
    {
        return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . $name . '.stub';
    }

    /**
     * 获取模型相关信息
     *
     * @param $module
     * @param $model
     * @param $table
     *
     * @return array
     */
    protected function getModelData($module, $model, $table, $columnList)
    {
        return $this->getParseNameData($module, $model, $table, 'model', $columnList);
    }

    /**
     * 获取控制器相关信息
     *
     * @param $module
     * @param $controller
     * @param $table
     *
     * @return array
     */
    protected function getControllerData($module, $controller, $table)
    {
        return $this->getParseControllerData($module, $controller, $table, 'controller');
        //        return $this->getParseNameData($module, $controller, $table, 'controller');
    }

    protected function getLogicData($module, $controller, $table)
    {
        return $this->getParseLogicData($module, $controller, $table, 'service');
    }

    protected function getParseLogicData($module, $name, $table, $type)
    {
        $arr = [];
        $parseName = Str::studly($name) . 'Service';
        $appNamespace = 'app';
        $parseNamespace = "{$appNamespace}\\common\\{$type}";

        $moduleDir = app()->getAppPath() . 'common' . DIRECTORY_SEPARATOR;
        $parseFile = $moduleDir . $type . DIRECTORY_SEPARATOR . ($arr ? implode(DIRECTORY_SEPARATOR, $arr) . DIRECTORY_SEPARATOR : '') . $parseName . '.php';
        $parseArr = $arr;
        $parseArr[] = $parseName;
        return [$parseNamespace, $parseName, $parseFile, $parseArr];
    }

    /**
     * 获取已解析相关信息
     *
     * @param $module
     * @param $name
     * @param $table
     * @param $type
     *
     * @return array
     */
    protected function getParseControllerData($module, $name, $table, $type)
    {
        $arr = [];
        $parseName = Str::studly($name);
        $appNamespace = 'app';
        $parseNamespace = $appNamespace . '\\' . $module . '\\' . $type . ($arr ? "\\" . implode("\\", $arr) : "");
        $moduleDir = app()->getAppPath();
        $parseFile = $moduleDir . $module . DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . $parseName . 'Controller.php';
        $parseArr = $arr;
        $parseArr[] = $parseName;
        return [$parseNamespace, $parseName, $parseFile, $parseArr];
    }

    /**
     * 获取验证器相关信息
     *
     * @param $module
     * @param $validate
     * @param $table
     *
     * @return array
     */
    protected function getValidateData($module, $validate, $table, $columnList)
    {
        return $this->getParseValidateData($module, $validate, $table, 'validate', $columnList);
    }

    /**
     * 获取已解析相关信息
     *
     * @param $module
     * @param $name
     * @param $table
     * @param $type
     *
     * @return array
     */
    protected function getParseValidateData($module, $name, $table, $type, $columnList)
    {
        $arr = [];
        $parseName = Str::studly($name) . 'Validate';
        $appNamespace = 'app';

        //        $parseNamespace = $appNamespace.'\\'.$module.'\\'.$type . ($arr ? "\\" . implode("\\", $arr) : "");
        //        $moduleDir = app()->getAppPath();
        //        $parseFile = $moduleDir . $module . DIRECTORY_SEPARATOR   . $type . DIRECTORY_SEPARATOR   . $parseName . 'Controller.php';
        //        $parseArr = $arr;
        //        $parseArr[] = $parseName;
        //        return [$parseNamespace, $parseName, $parseFile, $parseArr];
        //

        $parseNamespace = "{$appNamespace}\\{$module}\\{$type}" . ($arr ? "\\" . implode("\\", $arr) : "");
        $moduleDir = app()->getAppPath() . $module . DIRECTORY_SEPARATOR;
        $parseFile = $moduleDir . $type . DIRECTORY_SEPARATOR . ($arr ? implode(DIRECTORY_SEPARATOR, $arr) . DIRECTORY_SEPARATOR : '') . $parseName . '.php';
        $parseArr = $arr;
        $parseArr[] = $parseName;
        $notes = $this->getValidateNotes($columnList);
        return [$parseNamespace, $parseName, $parseFile, $parseArr, $notes];
    }

    public function getValidateNotes($columnList)
    {
        $str = '';
        foreach ($columnList as $v) {
            $comment = explode(' ', $v['COLUMN_COMMENT']);
            $str .= sprintf('\'%s\' => \'%s\' ,', $v['COLUMN_NAME'], $comment[0]) . PHP_EOL;
        }
        return $str;
    }


    /**
     * 获取已解析相关信息
     *
     * @param $module
     * @param $name
     * @param $table
     * @param $type
     *
     * @return array
     */
    protected function getParseNameData($module, $name, $table, $type, $columnList = [])
    {
        $arr = [];
        $parseName = Str::studly($name);
        $appNamespace = 'app';
        $parseNamespace = "{$appNamespace}\\{$module}\\{$type}" . ($arr ? "\\" . implode("\\", $arr) : "");
        $moduleDir = app()->getAppPath() . $module . DIRECTORY_SEPARATOR;
        $parseFile = $moduleDir . $type . DIRECTORY_SEPARATOR . ($arr ? implode(DIRECTORY_SEPARATOR, $arr) . DIRECTORY_SEPARATOR : '') . $parseName . '.php';
        $parseArr = $arr;
        $parseArr[] = $parseName;
        $notes = $this->getNotes($columnList);

        return [$parseNamespace, $parseName, $parseFile, $parseArr, $notes];
    }

    public function getNotes($columnList)
    {
        if (!$columnList) {
            return '';
        }
        $arr = ['int'];
        $str = '/**' . PHP_EOL;
        foreach ($columnList as $v) {
            $type = in_array($v['DATA_TYPE'], $arr) ? 'int' : 'string';
            $comment = explode(' ', $v['COLUMN_COMMENT']);
            $str .= sprintf('* @property %s $%s %s', $type, $v['COLUMN_NAME'], $v['COLUMN_COMMENT']) . PHP_EOL;
            //            $str .= sprintf('* @property %s $%s %s', $type, $v['COLUMN_NAME'], $comment[0]) . PHP_EOL;
        }
        $str .= '*/';
        return $str;
    }
}
